package com.shujia.zuoye

import scala.io.{BufferedSource, Source}


object HomeWork {
  //定义一些样例类，保存每一行的数据
  private case class Student(id: String, name: String, age: Int, gender: String, clazz: String)

  private case class Score(id: String, subject_id: String, score: Int)

  private case class Subject(subject_id: String, subject_name: String, subject_score: Int)

  //定义一些变量
  private var stuList: List[Student] = _
  private var scoreList: List[Score] = _
  private var subjectList: List[Subject] = _

  //定义一些Map变量，帮助后期的关联
  private var stuInfoMap: Map[String, String] = _
  private var subNameMap: Map[String, String] = _
  private var subScoreMap: Map[String, Int] = _

  private def loadData(): Unit = {
    //读取文件数据，将每一行封装成样例类到集合中
    val stuBS: BufferedSource = Source.fromFile("scala/data/students.csv")
    //处理读取到的学生数据，赋值给变量stuList
    stuList = stuBS.getLines()
      .toList // ["1500100978,郜昆卉,21,男,文科五班","xxx","xx"]
      .map((s: String) => {
        val strings: Array[String] = s.split(",")
        val id: String = strings(0)
        val name: String = strings(1)
        val age: Int = strings(2).toInt
        val gender: String = strings(3)
        val clazz: String = strings(4)
        //封装成一个Student类对象
        Student(id, name, age, gender, clazz)
      })
    //stuInfoMap
    //将学生的学号id作为键，姓名name和班级clazz作为value放入到Map集合中
    stuInfoMap = stuList.map((stu: Student) => (stu.id, stu.name + "," + stu.clazz)).toMap

    //读取文件数据，将每一行封装成样例类到集合中
    val scoreBS: BufferedSource = Source.fromFile("scala/data/score.txt")
    //处理读取到的学生数据，赋值给变量stuList
    scoreList = scoreBS.getLines()
      .toList // ["1500100978,郜昆卉,21,男,文科五班","xxx","xx"]
      .map((s: String) => {
        val strings: Array[String] = s.split(",")
        val id: String = strings(0)
        val subject_id: String = strings(1)
        val score: Int = strings(2).toInt
        //封装成一个Student类对象
        Score(id, subject_id, score)
      })


    //读取文件数据，将每一行封装成样例类到集合中
    val subjectBS: BufferedSource = Source.fromFile("scala/data/subject.csv")
    //处理读取到的学生数据，赋值给变量stuList
    subjectList = subjectBS.getLines()
      .toList // ["1500100978,郜昆卉,21,男,文科五班","xxx","xx"]
      .map((s: String) => {
        val strings: Array[String] = s.split(",")
        val subject_id: String = strings(0)
        val subject_name: String = strings(1)
        val subject_score: Int = strings(2).toInt
        //封装成一个Student类对象
        Subject(subject_id, subject_name, subject_score)
      })
    //使用科目编号subject_id作为键，科目的名字作为value值
    subNameMap = subjectList.map((subject: Subject) => (subject.subject_id, subject.subject_name)).toMap
    //使用科目编号subject_id作为键，科目总分单独作为value值
    subScoreMap = subjectList.map((subject: Subject) => (subject.subject_id, subject.subject_score)).toMap
  }

  //尝试将三个不同的数据进行关联 输出我们想要看的学生信息，传入的是我们想要看的学生编号组成的集合
  //学号，姓名，班级，科目，分数
  private def printResultWithId(ids: List[String]): Unit = {
    //基于分数表的数据，结合两个Map数据类型进行关联，利用了中间表的id关联
    scoreList.filter((s: Score) => ids.contains(s.id)) //过滤出函数传入的学生id集合
      .map((s: Score) => {
        //利用Score样例类中的id，在学生Map集合中找学生信息 name,clazz
        val nameWithClazz: String = stuInfoMap.getOrElse(s.id, "查无此人")
        //利用Score样例类中的subject_id，在科目名称Map集合中找科目名字信息 subject_name
        val subjectName: String = subNameMap.getOrElse(s.subject_id, "没有这个科目")
        s"${s.id},${nameWithClazz},${subjectName},${s.score}"
      }).foreach(println)
  }

  /**
   * 题目1：统计班级人数 [班级,人数]
   */
  private def clazzCount(): Unit = {
    stuList.groupBy((s: Student) => s.clazz) //根据班级进行分组
      .map((kv: (String, List[Student])) => kv._1 + "," + kv._2.size) //聚合
      .foreach(println)

//    val stringToStudents: Map[String, List[Student]] = stuList.groupBy((s: Student) => s.clazz)
  }

  /**
   * 题目2：统计学生的总分 [学号,学生姓名,学生年龄,总分]
   */
  private def studentSumScore(): Unit = {
    val studentSumScoreRes: Map[String, Int] = scoreList.groupBy((s: Score) => s.id) //根据学生的学号分组
      //(1500100112,List(Score(1500100112,1000001,33), Score(1500100112,1000002,7), Score(1500100112,1000003,136), Score(1500100112,1000007,62), Score(1500100112,1000008,49), Score(1500100112,1000009,72)))
      .map((kv: (String, List[Score])) => (kv._1, kv._2.map((s: Score) => s.score).sum))

    //与学生信息集合关联，根据学号id进行关联
    val stuSumScoreList: List[String] = stuList.map((s: Student) => {
      val sumScore: Int = studentSumScoreRes.getOrElse(s.id, 0)
      s"${s.id},${s.name},${s.age},$sumScore"
    })
    stuSumScoreList.foreach(println)
  }

  /**
   * 题目3：统计年级排名前十学生各科的分数 [学号,  姓名，班级，科目，分数]
   */
  private def studentSumScoreTop10(): Unit = {
    val ids: List[String] = scoreList.groupBy((s: Score) => s.id) //根据学号进行分组
      .map((kv: (String, List[Score])) => (kv._1, kv._2.map((s: Score) => s.score).sum))
      .toList
      .sortBy((kv: (String, Int)) => -kv._2)
      .take(10)
      .map((kv: (String, Int)) => kv._1)
    printResultWithId(ids)
  }

  /**
   * 题目4：统计总分大于年级平均分的学生 [学号，姓名，班级，总分]
   */
  private def sumScoreGAvgScore(): Unit = {
    //先求出年级总平均分 372.0
    val avgSumScore: Double = (scoreList.map(_.score).sum / stuList.size).toDouble

    //统计每个学生的总分
    scoreList.groupBy((s: Score) => s.id) //根据学号进行分组
      .map((kv: (String, List[Score])) => (kv._1, kv._2.map((s: Score) => s.score).sum))
      .filter((kv: (String, Int)) => kv._2 > avgSumScore) //过滤出总分大于年级平均分的
      .map((kv: (String, Int)) => {
        //通过学号与学生数据进行关联
        val nameWithClazz: String = stuInfoMap.getOrElse(kv._1, "查无此人")
        s"${kv._1},$nameWithClazz,${kv._2}"
      }).foreach(println)
  }

  /**
   * 题目5：统计每科都及格的学生 [学号，姓名，班级，科目，分数]
   * 一般来说是总分的60%
   */
  private def subjectPassStudents(): Unit = {
    //通过每一个Score中的科目编号拿到对应的科目总分
    val ids: List[String] = scoreList.filter((s: Score) => s.score >= subScoreMap.getOrElse(s.subject_id, 0) * 0.6)
      //统计每个学生及格的科目数
      .groupBy((s: Score) => s.id)
      .map((kv: (String, List[Score])) => (kv._1, kv._2.size))
      .filter((kv: (String, Int)) => kv._2 == 6)
      .keys //获取每门科目都及格的学生学号
      .toList

    printResultWithId(ids)
  }

  /**
   * 题目6：统计每个班级的前三名 [学号，姓名，班级，分数]
   */
  private def clazzSumScoreTop3(): Unit = {
    scoreList.groupBy((s: Score) => s.id) //根据学号进行分组
      .map((kv: (String, List[Score])) => (kv._1, kv._2.map((s: Score) => s.score).sum))
      //通过学号从学生的信息集合中将姓名和班级拿出来stuInfoMap
      .map((kv: (String, Int)) => {
        val nameWithClazz: String = stuInfoMap.getOrElse(kv._1, "查无此人")
        val strings: Array[String] = nameWithClazz.split(",")
        val name: String = strings(0)
        val clazz: String = strings(1)
        (kv._1, name, clazz, kv._2)
      })
      .groupBy(_._3) //根据班级进行分组
      .flatMap((kv: (String, Iterable[(String, String, String, Int)])) => {
        kv._2.toList.sortBy(-_._4).take(3)
      }).foreach(println)
  }

  /**
   * 题目7：统计偏科最严重的前100名学生  [学号，姓名，班级，科目，分数] 方差
   *
   * 每一个人的分数减去自己的平均分的总和除以科目数
   * 1500100001,1000001,98
   * 1500100001,1000002,5
   * 1500100001,1000003,137
   * 1500100001,1000004,29
   * 1500100001,1000005,85
   * 1500100001,1000006,52
   */
  private def partialBadlyStuTop100(): Unit = {
    //数据归一化，将数据处理到一个可控的范围中 0-100之间
    val ids: List[String] = scoreList.map((s: Score) => {
        (s.id, s.subject_id, s.score * 100.toDouble / subScoreMap.getOrElse(s.subject_id, 0))
      }).groupBy(_._1)
      .map((kv: (String, List[(String, String, Double)])) => {
        val id: String = kv._1 // 学号
        val scoreGuiList: List[(String, String, Double)] = kv._2
        //计算个人的平均分
        val stuGeRenAvgScore: Double = scoreGuiList.map(_._3).sum / scoreGuiList.size
        //计算方差
        val partial: Double = scoreGuiList.map((t: (String, String, Double)) => {
          Math.pow(t._3 - stuGeRenAvgScore, 2)
        }).sum / scoreGuiList.size
        (id, partial)
      }).toList
      .sortBy(-_._2)
      .take(100)
      .map(_._1)

    printResultWithId(ids)
  }


  def main(args: Array[String]): Unit = {
    loadData()

    //    /**
    //     * 1500100985,申飞珍,21,女,文科一班
    //     * 1500100986,左天曼,22,女,文科一班
    //     * 1500100987,双昆杰,24,男,文科四班
    //     * 1500100988,余鸿云,22,男,文科六班
    //     */
    //    val ids: List[String] = List("1500100985", "1500100986", "1500100987", "1500100988")
    //    printResultWithId(ids)
    //    clazzCount() //题目1
    //    studentSumScore() //题目2
    //    studentSumScoreTop10() //题目3
    //    sumScoreGAvgScore() //题目4
    //    subjectPassStudents() //题目5
    //    clazzSumScoreTop3() //题目6
    partialBadlyStuTop100() //题目7

  }
}
